<?php
/**
 * crcl/base
 *
 * Copyright 2013 CRCL - André Sabosch - info@crcl.de
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 * http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

/**
 * dealing with uris - this is the default class, could handle every uri
 * other containers, like the ini class, will add more specific methods
 *
 * generic syntax: URI = scheme ":" hier-part [ "?" query ] [ "#" fragment ]
 *
 * sample:
 * foo://username:password@example.com:8042/over/there/index.dtb?type=animal&name=narwhal#nose
 *
 * getScheme       = foo
 * getUserinfo     = username:password
 * getHost         = example.com
 * getPort         = 8042
 * getQuery        = type=animal&name=narwhal
 * getFragment     = nose
 * getAuthority    = username:password@example.com:8042
 * getPath         = /over/there/index.dtb
 * getParams       = ['type' => 'animal', 'name' => 'narwhal']
 * getHierarchical = username:password@example.com:8042/over/there/index.dtb
 *
 * urn:example:animal:ferret:nose
 *
 * getScheme       = urn
 * getUserinfo     =
 * getHost         = localhost
 * getPort         =
 * getQuery        =
 * getFragment     =
 * getAuthority    =
 * getPath         = example:animal:ferret:nose
 * getParams       =
 * getHierarchical = example:animal:ferret:nose
 *
 * @see http://en.wikipedia.org/wiki/URI_scheme#Generic_syntax
 */

namespace crcl\base\resource\reader;

interface IUri
{
  /**
   * Returns scheme of URI, always lowercase
   * URI starts with scheme to define type of uri
   * terminated by a colon (:)
   *
   * @return  string
   */
  public function getScheme();

  /**
   * Returns path of URI
   * if authority is set, path must begin with '/'
   * if no authority is set, path cannot start with '//'
   * @return  string
   */
  public function getPath();

  /**
   * Returns host of URI
   *
   * @return string
   */
  public function getHost();

  /**
   * Returns port of URI
   *
   * @return int
   */
  public function getPort();

  /**
   * Return authority of URI (host:port)
   *
   * @return string
   */
  public function getAuthority();

  /**
   * Returns params of URI, converted to array.
   * If no params are used, an empty array is returned
   *
   * @return array  Params
   */
  public function getParams();


  /**
   * Returns param of URI, as written in URI */
  public function getQuery();

  /*
  public function getUserinfo();

  public function getFragment();

  public function getHierarchical();
  */
}

class ContainerUri implements IUri
{
  public function __construct($sUri)
  {
    $sUri = \trim($sUri);
    $this->sUri = $sUri;

    $this->sScheme     = $this->resolveScheme($sUri);
    $this->sAuthority = $this->resolveAuthority($sUri);
    $this->sPath      = $this->resolvePath($sUri);
    $this->sQuery     = $this->resolveQuery($sUri);
    //$this->_sFragment  = $this->_resolveFragment($sUri);
  }

  public function getScheme()
  {
    return $this->sScheme;
  }

  public function getPath()
  {
    if (\is_null($this->sPath))
    {
      throw new Exception(
        'No path',
        Exception::ERROR_URI_NOPATH, array('uri' => $this->sUri));
    }
    $this->sPath = $this->decodePath($this->sPath);
    return $this->sPath;
  }

  public function getAuthority()
  {
    return $this->sAuthority;
  }

  public function getHost()
  {
    $iPos = \strpos($this->sAuthority, ':');
    if ($iPos !== \FALSE)
    {
      $sHost = \substr($this->sAuthority, 0, $iPos);
    }
    else
    {
      $sHost = $this->sAuthority;
    }
    return $sHost;
  }

  public function getPort()
  {
    $iPos = \strpos($this->sAuthority, ':');
    if ($iPos !== \FALSE)
    {
      $sPort = \substr($this->sAuthority, $iPos+1);
    }
    else
    {
      throw new Exception(
        'No port.',
        Exception::ERROR_URI_NOPORT,
        array('uri' => $this->sUri, 'authority' => $this->sAuthority));
    }
    if (\is_numeric($sPort))
    {
      return (int)$sPort;
    }
    throw new Exception(
      'Invalid port.',
      Exception::ERROR_URI_INVALIDPORT,
      array('uri' => $this->sUri, 'authority' => $this->sAuthority, 'port' => $sPort));
  }

  public function getQuery()
  {
    if (\is_null($this->sQuery))
    {
      throw new Exception('No query.',
        Exception::ERROR_URI_NOQUERY, array('uri' => $this->sUri));
    }
    return $this->sQuery;
  }

  public function getParams()
  {
    $aParams = [];
    if (!\is_null($this->sQuery) && (!empty($this->sQuery)))
    {
      $aQuery = \explode('&', $this->sQuery);
      foreach ($aQuery as $sParam)
      {
        $aItem = \explode('=', $sParam);
        $aParams[$aItem[0]] = $aItem[1];
      }
    }
    return $aParams;
  }

  /** PROTECTED ***********************************************************************************/

  protected $sAuthority = \NULL;
  protected $sUri;

  /** PRIVATE *************************************************************************************/

  private $sScheme;
  private $sPath = \NULL;
  private $sQuery = \NULL;

  private function resolveScheme($sUri)
  {
    //every URI should contain a scheme. If not, this class could not be loaded (see factory)
    $iPos = \strpos($sUri, ':');
    $sScheme = \substr($sUri, 0, $iPos);
    $sScheme = \strtolower($sScheme);
    return $sScheme;
  }

  private function resolveAuthority($sUri)
  {
    $sAuthority = \crcl\base\String::getInstance($sUri)->replaceFirst($this->sScheme, '');
    $iPos = \strpos($sAuthority, '//');
    if ($iPos === \FALSE)
    {
      return \NULL; //no authority
    }
    $sAuthority = \substr($sAuthority, $iPos+2);
    $iLength = \strlen($sAuthority);
    $iPos1 = \strpos($sAuthority, '/');

    //Achtung: Slash vor @ gehört zum User:Passwort
    $iPosAt = \strpos($sAuthority, '@');
    if ($iPosAt !== \FALSE)
    {
      if ($iPos1 < $iPosAt)
      {
        $iPos1 = \strpos($sAuthority, '/', $iPosAt);
      }
    }

    if ($iPos1 === \FALSE)
    {
      $iPos1 = $iLength;
    }
// cant find sample... why? //TODO - old code. not needed else?
//    else
//    {
//      if (\strpos($sAuthority, '@') !== \FALSE)
//      {
//        if ($iPos1 < \strpos($sAuthority, '@'))
//        {
//          $iPos1 = $iLength;
//        }
//      }
//    }
    $iPos2 = \strpos($sAuthority, '?');
    if ($iPos2 === \FALSE)
    {
      $iPos2 = $iLength;
    }
    $iPos3 = \strpos($sAuthority, '#');
    if ($iPos3 === \FALSE)
    {
      $iPos3 = $iLength;
    }
    $iPos = \min($iPos1, $iPos2, $iPos3);
    $sAuthority = \substr($sAuthority, 0, $iPos);
    $sAuthority = \trim($sAuthority);
    if ($sAuthority == '')
    {
      $sAuthority = \NULL;
    }
    return $sAuthority;
  }

  private function resolvePath($sUri)
  {
    $sPath = \crcl\base\String::getInstance($sUri)->replaceFirst($this->sScheme, '');

    if (!\is_null($this->sAuthority))
    {
      $sPath->replaceFirst($this->sAuthority, '');
    }
    $sPath->doNotStartWith(':');
    if (\strpos($sPath, '//') === 0)
    {
      $sPath->doNotStartWith('//');
    }
    else
    {
      $sPath->doNotStartWith('/');
    }
    $sPath->doNotStartWith('@');
    $iLength = \strlen($sPath);
    $iPos1 = \strpos($sPath, '?');
    if ($iPos1 === \FALSE)
    {
      $iPos1 = $iLength;
    }
    $iPos2 = \strpos($sPath, '#');
    if ($iPos2 === \FALSE)
    {
      $iPos2 = $iLength;
    }
    $iPos = \min($iPos1, $iPos2);
    $sPath = \substr($sPath, 0, $iPos);
    $sPath = \trim($sPath);
    if ($sPath == '')
    {
      $sPath = \NULL;
    }
    return $sPath;
  }

  private function decodePath($sPath)
  {
    $sPath = \rawurldecode($sPath);

    $sPath = \crcl\base\base\Path::replaceConstants($sPath);
    $sPath = \crcl\base\application\Path::replaceConstants($sPath);
    $sPath = \crcl\base\Tier::replaceConstants($sPath, \TRUE);

    if (\strlen($sPath) > 2)
    {
     // if (strpos($sPath, 'X:') !== FALSE)      var_dump($sPath);
      if ($sPath[1] == ':') //Drive letter
      {
        $sPath = \crcl\base\String::getInstance($sPath)
          ->doNotStartWith('/')
          ->toString();
      }
    }
    return $sPath;
  }

  private function resolveQuery($sUri)
  {
    $sQuery = \NULL;
    $iPos = \strpos($sUri, '?');
    if ($iPos !== \FALSE)
    {
      $sQuery = \substr($sUri, $iPos+1);
      $iPosFragment = \strpos($sQuery, '#');
      if ($iPosFragment !== \FALSE)
      {
        $sQuery = \substr($sQuery, 0, $iPosFragment);
      }
    }

    return $sQuery;
  }
}